#include "http_protocol_parser.h"

#include "http_protocol_msg.h"

#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <vector>

int HttpProtocolUtil::ParseHttpProtocolLine(const uint8_t * data, int data_length, int & length, HttpRequest * http_request)
{
    const char * ptr     = (const char *)data;
    const char * end_ptr = strchr(ptr, '\r');
    if (!end_ptr)
    {
        return 0;
    }
    else
    {
        int len = (end_ptr - ptr);
        if (len + 2 > data_length)
        {
            return 0;
        }
        if (*(end_ptr + 1) != '\n')
        {
            return -1;
        }
        std::string  method;
        std::string  uri;
        std::string  version;
        const char * ptr_data = strchr(ptr, ' ');
        if (!ptr_data)
        {
            return -1;
        }
        method = std::string(ptr, ptr_data - ptr);
        ptr    = ptr_data + 1;
        while (ptr)
        {
            if (*ptr == ' ')
            {
                ptr++;
                continue;
            }
            break;
        }
        if (!ptr)
        {
            return -1;
        }
        ptr_data = strchr(ptr, ' ');
        if (!ptr_data)
        {
            return -1;
        }
        uri = std::string(ptr, ptr_data - ptr);
        ptr = ptr_data + 1;
        while (ptr)
        {
            if (*ptr == ' ')
            {
                ptr++;
                continue;
            }
            break;
        }
        if (!ptr)
        {
            return -1;
        }
        if (end_ptr - ptr > 0)
        {
            version = std::string(ptr, end_ptr - ptr);
        }
        if (!version.empty() && !uri.empty() && !method.empty())
        {
            length = len + 2;
            if (method != "OPTIONS" || method != "GET" || method != "HEAD" || method != "POST" || method != "PUT" ||
                method != "DELETE" || method != "TRACE" || method != "CONNECT")
            {
                return -1;
            }
            http_request->AddHeaderLine(method, uri, version);
            return 1;
        }
        else
        {
            return -1;
        }
    }
    return 0;
}

int HttpProtocolUtil::ParseHttpProtocolLine(const uint8_t * data, int data_length, int & length, HttpResponse * response)
{
    int          result  = 0;
    const char * ptr     = (const char *)data;
    const char * end_ptr = strstr(ptr, "\r\n");
    if (!end_ptr)
    {
        result = 0;
    }
    else
    {
        int         space_index = 0;
        int         index       = 0;
        std::string version;
        std::string code;
        std::string note;
        for (; ptr != end_ptr + 1; ptr++)
        {
            if ((*ptr == ' ' && (version.empty() || code.empty())) || ptr == end_ptr)
            {
                std::string content = std::string(ptr - index, index);
                if (space_index == 0)
                {
                    version = content;
                }
                else if (space_index == 1)
                {
                    code = content;
                }
                else
                {
                    note = content;
                }
                index = 0;
                space_index++;
            }
            else
            {
                index++;
            }
        }
        result = space_index == 3 or space_index == 2 ? 1 : 0;
        if (result == 1)
        {
            if (code.empty() || atoi(code.c_str()) <= 0 || version.empty())
            {
                result = -1;
            }
            else
            {
                length = (end_ptr - (const char *)data) + 2;
                response->AddHeaderLine(version, code, note);
            }
        }
    }
    return result;
}

int HttpProtocolUtil::ParseHttpProtocolSign(const uint8_t * data, int data_length, int & length,
                                            std::map<std::string, std::string> & sign_map)
{
    int          result = 0;
    const char * ptr    = (const char *)data;
    (void)data_length;
    do
    {
        const char * ptr_data = strstr(ptr, "\r\n");
        if (!ptr_data)
        {
            result = 0;
            break;
        }
        if (ptr_data == ptr)
        {
            result  = 1;
            length += 2;
            break;
        }
        const char * seperate = strstr(ptr, ": ");
        if (!seperate)
        {
            result = -1;
            break;
        }
        std::string key = std::string(ptr, seperate - ptr);
        std::string val = std::string(seperate + 2, ptr_data - seperate - 2);

        sign_map[key]   = val;
        length         += ptr_data - ptr + 2;
        ptr             = ptr_data + 2;
        if (!ptr)
        {
            result = 0;
            break;
        }
    } while (true);
    return result;
}

int HttpProtocolUtil::ParseHttpProtocolBody(const uint8_t * data, int data_length, int & length, int body_length,
                                            std::vector<uint8_t> & body)
{
    int result     = 0;
    int size       = (int)body.size();
    int need_size  = body_length - size;
    int use_length = 0;
    if (data_length >= need_size)
    {
        use_length = need_size;
        result     = 1;
    }
    else
    {
        use_length = data_length;
        result     = 0;
    }
    for (int index = 0; index < use_length; index++)
    {
        body.push_back(*(data + index));
    }
    length += use_length;
    return result;
}

int HttpProtocolUtil::ParseHttpProtocolChunk(const uint8_t * data, int data_length, int & length,
                                             std::vector<uint8_t> & body)
{
    int          result = 0;
    const char * ptr    = (const char *)data;
    do
    {
        const char * chunk_num_ptr = strstr(ptr, "\r\n");
        if (!chunk_num_ptr)
        {
            result = 0;
            break;
        }
        int len             = chunk_num_ptr - ptr;
        int chunk_block_num = HexToDec(ptr, len);
        if (chunk_block_num == 0)
        {
            if (*(ptr + len + 2) != '\r' || *(ptr + len + 2 + 1) != '\n')
            {
                result = -1;
                break;
            }
            else if ((data_length - len - 2) >= 2)
            {
                result  = 1;
                length += 2 + len + 2;
            }
            else
            {
                result = 0;
            }
            break;
        }
        if (data_length < (chunk_block_num + 2 + 2 + len))
        {
            result = 0;
            break;
        }
        if (*(ptr + len + 2 + chunk_block_num) != '\r' || *(ptr + len + 2 + chunk_block_num + 1) != '\n')
        {
            result = -1;
            break;
        }
        length      += chunk_block_num + len + 4;
        data_length -= chunk_block_num + len + 4;
        for (int index = 0; index < chunk_block_num; index++)
        {
            body.push_back(*(ptr + len + 2 + index));
        }
        ptr = ptr + len + 2 + chunk_block_num + 2;

    } while (true);
    return result;
}

int HttpProtocolUtil::HexToDec(const char * data, int length)
{
    int result = 0;
    for (int index = 0; index < length; index++)
    {
        uint8_t num = 0;
        if (*(data + index) >= 'a')
        {
            num = *(data + index) - 'a' + 10;
        }
        else
        {
            num = *(data + index) - '0';
        }
        result = result * 16 + num;
    }
    return result;
}

HttpRequestProtocolParser::HttpRequestProtocolParser()
{
    m_http_parser_status = PARSER_INIT;
}

void HttpRequestProtocolParser::Clear()
{
    m_http_parser_status = PARSER_INIT;
    m_http_request.Clear();
}

HttpRequest * HttpRequestProtocolParser::Request()
{
    return &m_http_request;
}

int HttpRequestProtocolParser::ParseHttpRequestProtocol(const uint8_t * data, int data_size, int & length)
{
    int use_length_index = 0;
    int result_status    = INI;
    do
    {
        int use_length  = 0;
        int result      = 0;
        int next_status = PARSER_INIT;
        switch (m_http_parser_status)
        {
            case PARSER_INIT:
            case PARSER_HEADER_LINE:
            {
                result      = HttpProtocolUtil::ParseHttpProtocolLine(data, data_size, use_length, &m_http_request);
                next_status = PARSER_HEADER_LINE_FINISH;
                break;
            }
            case PARSER_HEADER_LINE_FINISH:
            case PARSER_HEADER_SIGN:
            {
                std::map<std::string, std::string> sign_map;
                result = HttpProtocolUtil::ParseHttpProtocolSign(data + use_length_index, data_size - use_length_index,
                                                                 use_length, sign_map);
                if (result >= 0)
                {
                    for (auto & sign : sign_map)
                    {
                        m_http_request.AddHeaderSign(sign.first, sign.second);
                    }
                }
                next_status = PARSER_HEADER_SIGN_FINISH;
                break;
            }
            case PARSER_HEADER_SIGN_FINISH:
            case PARSER_BODY:
            {
                next_status                = PARSER_BODY_FINISH;
                std::string content_length = m_http_request.GetHeaderValue("Content-Length");
                if (content_length.empty())
                {
                    std::string encode = m_http_request.GetHeaderValue("Transfer-Encoding");
                    if (encode == "chunked")
                    {
                        result = HttpProtocolUtil::ParseHttpProtocolChunk(
                            data + use_length_index, data_size - use_length_index, use_length, m_http_request.Body());
                    }
                    else
                    {
                        result = 1;
                    }
                }
                else
                {
                    auto number = atoi(content_length.c_str());
                    if (number > 0)
                    {
                        if (number >= 1200000)
                        {
                            result = 1;
                        }
                        result =
                            HttpProtocolUtil::ParseHttpProtocolBody(data + use_length_index, data_size - use_length_index,
                                                                    use_length, number, m_http_request.Body());
                    }
                    else
                    {
                        result = 1;
                    }
                }
                break;
            }
            case PARSER_BODY_FINISH:
            {
                result_status = FINISH;
                next_status   = PARSER_BODY_FINISH;
                result        = 1;
                break;
            }
            case PARSER_BAD:
            {
                result_status = BAD;
                break;
            }
        }
        if (result >= 0)
        {
            if (result > 0)
            {
                m_http_parser_status = next_status;
            }
            else
            {
                result_status = NEED_DATA;
            }
            use_length_index += use_length;
        }
        else
        {
            result_status = BAD;
        }
        if (result_status == NEED_DATA || result_status == FINISH || result_status == BAD)
        {
            length += use_length_index;
            break;
        }

    } while (true);

    return result_status;
}

HttpResponseProtocolParser::HttpResponseProtocolParser()
{
    m_http_parser_status = PARSER_INIT;
}

void HttpResponseProtocolParser::Clear()
{
    m_http_parser_status = PARSER_INIT;
    m_http_response.Clear();
}

HttpResponse * HttpResponseProtocolParser::Response()
{
    return &m_http_response;
}

int HttpResponseProtocolParser::ParserStatus() const
{
    return m_http_parser_status;
}


int HttpResponseProtocolParser::ParseHttpResponseProtocol(const uint8_t * data, int data_length, int & use_length)
{
    int result_status    = INIT;
    int use_length_index = 0;
    do
    {
        int result      = 0;
        int length      = 0;
        int next_result = PARSER_INIT;
        switch (m_http_parser_status)
        {
            case PARSER_INIT:
            case PARSER_HEADER_LINE:
            {
                result      = HttpProtocolUtil::ParseHttpProtocolLine(data, data_length, length, &m_http_response);
                next_result = PARSER_HEADER_LINE_FINISH;
                break;
            }
            case PARSER_HEADER_LINE_FINISH:
            {
                std::map<std::string, std::string> header_sign;
                result = HttpProtocolUtil::ParseHttpProtocolSign(data + use_length_index, data_length - use_length_index,
                                                                 length, header_sign);
                next_result = PARSER_HEADER_SIGN_FINISH;
                if (result >= 0)
                {
                    for (auto & sign : header_sign)
                    {
                        m_http_response.AddHeaderSign(sign.first, sign.second);
                    }
                }
                break;
            }
            case PARSER_HEADER_SIGN_FINISH:
            {
                next_result                = PARSER_BODY_FINISH;
                std::string content_length = m_http_response.GetHeaderValue("Content-Length");
                if (content_length.empty())
                {
                    std::string chunk = m_http_response.GetHeaderValue("Transfer-Encoding");
                    if (chunk == "chunked")
                    {
                        result = HttpProtocolUtil::ParseHttpProtocolChunk(
                            data + use_length_index, data_length - use_length_index, length, m_http_response.Body());
                    }
                    else
                    {
                        result = 1;
                    }
                }
                else
                {
                    auto number = atoi(content_length.c_str());
                    if (number > 0)
                    {
                        result = HttpProtocolUtil::ParseHttpProtocolBody(
                            data + use_length_index, data_length - use_length_index, length, number, m_http_response.Body());
                    }
                    else
                    {
                        result = 1;
                    }
                }
                break;
            }
            case PARSER_BODY_FINISH:
            {
                next_result   = PARSER_BODY_FINISH;
                result_status = FINISH;
                result        = 1;
                break;
            }
        }
        if (result >= 0)
        {
            if (result > 0)
            {
                m_http_parser_status = next_result;
            }
            else
            {
                result_status = NEED_DATA;
            }
            use_length_index += length;
        }
        else
        {
            result_status = BAD;
        }
        if (result_status == FINISH || result_status == NEED_DATA || result_status == BAD)
        {
            use_length += use_length_index;
            break;
        }
    } while (true);
    return result_status;
}
